package com.lp.mgt.configs.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.swing.plaf.basic.BasicScrollPaneUI.HSBChangeListener;


@Configuration
public class ShiroConfig {
/*
 *  /admins/**=anon               # 表示该 uri 可以匿名访问
	/admins/**=auth               # 表示该 uri 需要认证才能访问
	/admins/**=authcBasic         # 表示该 uri 需要 httpBasic 认证
	/admins/**=perms[user:add:*]  # 表示该 uri 需要认证用户拥有 user:add:* 权限才能访问
	/admins/**=port[8081]         # 表示该 uri 需要使用 8081 端口
	/admins/**=rest[user]         # 相当于 /admins/**=perms[user:method]，其中，method 表示  get、post、delete 等
	/admins/**=roles[admin]       # 表示该 uri 需要认证用户拥有 admin 角色才能访问
	/admins/**=ssl                # 表示该 uri 需要使用 https 协议
	/admins/**=user               # 表示该 uri 需要认证或通过记住我认证才能访问
	/logout=logout                # 表示注销,可以当作固定配置
 * */   
    @Bean
    public ShiroFilterFactoryBean  shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
/*        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/imgs/**", "anon");
        filterChainDefinitionMap.put("/fonts/**", "anon");
        filterChainDefinitionMap.put("/bootstrap/**", "anon");*/
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/sendMsg/*", "anon");
        filterChainDefinitionMap.put("/toResetPassword", "anon");//跳转修改密码界面
        filterChainDefinitionMap.put("/resetPage", "anon");//跳转修改密码界面
        filterChainDefinitionMap.put("/login", "anon");//登录方法可以匿名访问，不然永远登不上去
        filterChainDefinitionMap.put("/loginPage", "anon");//登录页面
        filterChainDefinitionMap.put("/getPicCode", "anon");
        //filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/user/*", "anon");
        filterChainDefinitionMap.put("/logout", "logout");//固定配置
        filterChainDefinitionMap.put("/mainPage", "user");
        filterChainDefinitionMap.put("/**", "user");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
        shiroFilterFactoryBean.setLoginUrl("/loginPage");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/mainPage");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    	return shiroFilterFactoryBean;
    }

    //用于thymeleaf模板使用shiro标签
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }   
    
    /**
     * 哈希密码比较器。在myShiroRealm中作用参数使用
     * 登陆时会比较用户输入的密码，跟数据库密码配合盐值salt解密后是否一致。
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用md5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数，比如散列两次，相当于 md5( md5(""));
        //hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }    
    
    
    /**
     * 身份认证realm; (这个需要自己写，账号密码校验；权限等)
     *
     * @return
     */
    @Bean
    public ShiroRealm myShiroRealm() {
    	ShiroRealm myShiroRealm = new ShiroRealm();
    	 myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    	return myShiroRealm;
    }
   
    
    /**
     * cookie对象;
     * 记住密码实现起来也是比较简单的，主要看下是如何实现的。
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe_mgt");
        simpleCookie.setHttpOnly(true);
        //simpleCookie.setDomain("");
        simpleCookie.setPath("/");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(604800);
        return simpleCookie;
    }
    
    /**
     * cookie管理对象;
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
        return cookieRememberMeManager;
    }    

    //缓存管理
    @Bean
    public EhCacheManager cacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
        return cacheManager;
    }


    /**
     * 核心的安全事务管理器
     * @return
     */   
    @Bean
    public SecurityManager  securityManager() {
    	DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager ();
        securityManager.setRealm(myShiroRealm());// 设置自定义realm
        securityManager.setCacheManager(cacheManager());//缓存
        //securityManager.setSessionManager(sessionManager());//会话
        securityManager.setRememberMeManager(rememberMeManager());//注入记住我管理器;
        return securityManager;
    }    
    
    
    //会话管理器
    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setGlobalSessionTimeout(1 * 60 * 60 * 1000);//session过期时间
        sessionManager.setDeleteInvalidSessions(true);//是否删除过期session
        sessionManager.setSessionIdCookie(rememberMeCookie());
        return sessionManager;
    }
    
    /***
     * 授权所用配置
     *
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }


    /**
     * Shiro生命周期处理器
     *
     */
    @Bean
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    
    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

/*
 * shiro认证流程

应用程序根据用户的身份和凭证（principals和credentials）来构造出AuthenticationToken实例，并调用Subject.login的方法进行登录，其会自动委托给Security
Manager。

Subject实例通常上都是DelegatingSubject
(或子类)，在验证开始的时候，Subject实例会将验证委托给应用程序配置的SecurityManager，并调用securityManager.login(token)方法进行身份认证。

SecurityManager得到token信息后，通过调用authenticator.authenticate(token)方法，把身份验证委托给内置的Authenticator的实例进行验证。authenticator通常是ModularRealmAuthenticator
实例，支持对一个或多个Realm实例进行适配。ModularRealmAuthenticator提供了一种可插拔的认证风格，你可以在此处插入自定义Realm实现。

如果配置了多个Realm，ModularRealmAuthenticator会根据配置的AuthenticationStrategy（身份验证策略）进行多Realm认证过程。
注：如果应用程序中仅配置了一个Realm，Realm将被直接调用而无需再配置认证策略。

判断每个Realm是否支持提交的token，如果支持Realm就会调用getAuthenticationInfo（token）方法进行认证处理

 * 
 */